package org.syntaxlisp.core.element;

import java.util.*;

public class MapScopeElement extends SyntaxLispElement implements CharacterScope {

    private final boolean[] characterMap;

    public MapScopeElement (int maxCodePoint) {
        super(SyntaxLispElementType.SCOPE, null);
        characterMap = new boolean[maxCodePoint + 1];
    }

    public MapScopeElement(int startCodePoint, int endCodePoint, int maxCodePoint) {
        super(SyntaxLispElementType.SCOPE, null);
        if (startCodePoint >= endCodePoint) {
            throw new IllegalArgumentException("Start code point can not larger than end code point.");
        }
        characterMap = new boolean[maxCodePoint + 1];
        Arrays.fill(characterMap, startCodePoint, endCodePoint + 1, true);
    }

    public MapScopeElement(CharacterScope scope) {
        super(SyntaxLispElementType.SCOPE, null);
        characterMap = new boolean[scope.getMaxCodePoint() + 1];
        add(scope);
    }


    public MapScopeElement (List<SyntaxLispElement> scopes, int maxCodePoint) {
        super(SyntaxLispElementType.SCOPE, null);
        characterMap = new boolean[maxCodePoint + 1];
        scopes.forEach(scope -> {
            if (scope instanceof CharacterScope) {
                add((CharacterScope) scope);
            } else {
                if (SyntaxLispElementType.CHARACTER == scope.getType()) {
                    add(((CharacterElement) scope).getCodepoint());
                } else {
                    throw new IllegalArgumentException("Unaccepted element type " + scope
                                                               + " when construct with element list.");
                }
            }
        });
    }

    @Override
    public boolean isInScope(char character) {
        return characterMap[character];
    }

    @Override
    public void add(char character) {
        characterMap[character] = true;
    }

    @Override
    public void add(int codePoint) {
        characterMap[codePoint] = true;
    }

    @Override
    public void add(CharacterScope characterScope) {
        for (int i = 0; i < characterMap.length; i ++) {
            characterMap[i] |= characterScope.isInScope((char) i);
        }
    }

    @Override
    public void remove(CharacterScope removeScope) {
        for (int i = 0; i < characterMap.length; i ++) {
            characterMap[i] = !removeScope.isInScope((char) i) && characterMap[i];
        }
    }

    @Override
    public void remove(char character) {
        characterMap[character] = false;
    }

    @Override
    public void remove(int codePoint) {
        characterMap[codePoint] = false;
    }

    @Override
    public List<int[]> getAllCodePointScopes() {
        List<int[]> codePointScopes = new ArrayList<>();
        int i = 0;
        while (i < characterMap.length) {
            while (i < characterMap.length
                    && !characterMap[i]) {
                i += 1;
            }
            if (i < characterMap.length) {
                int s = i;
                while (i < characterMap.length
                        && characterMap[i]) {
                    i += 1;
                }
                codePointScopes.add(new int[]{s, (i < characterMap.length) ? i : (characterMap.length - 1)});
            }
        }
        return codePointScopes;
    }

    @Override
    public int getMaxCodePoint() {
        return characterMap.length - 1;
    }

    @Override
    public boolean hasIntersection(CharacterScope scope) {
        for (int i = 0; i < characterMap.length; i ++) {
            if (characterMap[i] && scope.isInScope((char) i)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Set<Integer> getAllCodepointInScope() {
        Set<Integer> res = new HashSet<>();
        for (int i = 0; i < characterMap.length; i ++) {
            if (characterMap[i]) {
                res.add(i);
            }
        }
        return res;
    }

    @Override
    public SyntaxLispElement copy() {
        return (new MapScopeElement(this)).copyMatchPropertiesAndReturn(this);
    }

    @Override
    public boolean equalsTo(SyntaxLispElement o) {
        return false;
    }
}
